package k8s

import (
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"fmt"

	"k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	"k8s.io/apimachinery/pkg/fields"
)



type NodeAllocatedResources struct {
	// CPURequests is number of allocated milicores.
	CPURequests int64 `json:"cpuRequests"`

	// CPURequestsFraction is a fraction of CPU, that is allocated.
	CPURequestsFraction float64 `json:"cpuRequestsFraction"`

	// CPULimits is defined CPU limit.
	CPULimits int64 `json:"cpuLimits"`

	// CPULimitsFraction is a fraction of defined CPU limit, can be over 100%, i.e.
	// overcommitted.
	CPULimitsFraction float64 `json:"cpuLimitsFraction"`

	// CPUCapacity is specified node CPU capacity in milicores.
	CPUCapacity int64 `json:"cpuCapacity"`

	// MemoryRequests is a fraction of memory, that is allocated.
	MemoryRequests int64 `json:"memoryRequests"`

	// MemoryRequestsFraction is a fraction of memory, that is allocated.
	MemoryRequestsFraction float64 `json:"memoryRequestsFraction"`

	// MemoryLimits is defined memory limit.
	MemoryLimits int64 `json:"memoryLimits"`

	// MemoryLimitsFraction is a fraction of defined memory limit, can be over 100%, i.e.
	// overcommitted.
	MemoryLimitsFraction float64 `json:"memoryLimitsFraction"`

	// MemoryCapacity is specified node memory capacity in bytes.
	MemoryCapacity int64 `json:"memoryCapacity"`

	// AllocatedPods in number of currently allocated pods on the node.
	AllocatedPods int `json:"allocatedPods"`

	// PodCapacity is maximum number of pods, that can be allocated on the node.
	PodCapacity int64 `json:"podCapacity"`

	// PodFraction is a fraction of pods, that can be allocated on given node.
	PodFraction float64 `json:"podFraction"`
}


type K8sNode struct {
	Name string							`json:"name"`
	Annotations map[string]string		`json:"annotations"`
	Labels map[string]string			`json:"labels"`
	Ip string							`json:"ip"`
	Status string						`json:"status"`
	Cpu int64							`json:"cpu"`
	Memory int64						`json:"memory"`
	JoinDate string						`json:"joindate"`
	UnSchedulable bool 					`json:"unschedulable"`

	System string						`json:"system"`
	DockerVersion string				`json:"dockerversion"`
	CoreVersion string					`json:"coreversion"`
	KubeletVersion string				`json:"kubeletversion"`
	KubeProxyVersion string				`json:"kubeproxyversion"`
	SystemVersion string				`json:"systemversion"`
	SystemArch string					`json:"systemarch"`

	CurrentStatus []v1.NodeCondition	`json:"currentstatus"`

	Pods []K8sPod						`json:"pods"`
	PodNum	int							`json:"podnum"`
	ResourceRequestAndLimits NodeAllocatedResources 	`json:"resourceRequestAndLimits"`
}

type nodeIp string   //ip addr


func GetKubernetesNodes()(k8snodes []K8sNode , err error)  {
	nodes, err := clientset.CoreV1().Nodes().List(metav1.ListOptions{})
	if err != nil {
		fmt.Println("k8s get node err is ",err)
	}

	for _,node := range nodes.Items{
		fmt.Println(node.Labels)
		cpu,_ := node.Status.Allocatable.Cpu().AsInt64()
		memory,_ := node.Status.Allocatable.Memory().AsInt64()
		memory = memory / 1024 / 1024 / 1024 + 1
		nodeInformation	:= K8sNode{
			Name: node.Name,
			Labels: node.Labels,
			Ip: node.Status.Addresses[0].Address,
			Status: string(node.Status.Conditions[len(node.Status.Conditions)-1].Type),
			Cpu: cpu,
			Memory: memory,
			JoinDate: node.CreationTimestamp.Format("2006-01-02"),
			//System: node.Status.NodeInfo.OSImage,
			//DockerVersion: node.Status.NodeInfo.ContainerRuntimeVersion,
			//Pods: nodeIp(node.Status.Addresses[0].Address).pods(),

		}
		k8snodes = append(k8snodes,nodeInformation)

	}

	return
}



func GetKubernetesNodeDetail(name string)(k8snode K8sNode,events []Event, err error)  {

	fmt.Println(name)

	node, err := clientset.CoreV1().Nodes().Get(name,metav1.GetOptions{})
	if err != nil {
		fmt.Println("k8s get node err is ",err)
	}


			cpu,_ := node.Status.Allocatable.Cpu().AsInt64()
			memory,_ := node.Status.Allocatable.Memory().AsInt64()
			memory = memory / 1024 / 1024 / 1024 + 1
			p,nump,resource := nodeIp(node.Status.Addresses[0].Address).podsAndResource(node)
			k8snode	= K8sNode{
				Name: node.Name,
				Annotations: node.Annotations,
				Labels: node.Labels,
				Ip: node.Status.Addresses[0].Address,
				Status: string(node.Status.Conditions[len(node.Status.Conditions)-1].Type),
				Cpu: cpu,
				Memory: memory,
				JoinDate: node.CreationTimestamp.Format("2006-01-02"),
				UnSchedulable: node.Spec.Unschedulable,

				System: node.Status.NodeInfo.OSImage,
				DockerVersion: node.Status.NodeInfo.ContainerRuntimeVersion,
				CoreVersion: node.Status.NodeInfo.OperatingSystem,
				KubeletVersion: node.Status.NodeInfo.KubeletVersion,
				KubeProxyVersion: node.Status.NodeInfo.KubeProxyVersion,
				SystemVersion: node.Status.NodeInfo.KernelVersion,
				SystemArch: node.Status.NodeInfo.Architecture,

				CurrentStatus: node.Status.Conditions,

				Pods: p ,
				PodNum: nump,

				ResourceRequestAndLimits: resource,

			}

	events = getEventsForNode(node)




	//fmt.Println("resource is ")
	////fmt.Printf("%+v\n",resource)
	return
}




func (n nodeIp) podsAndResource(node *v1.Node) (podSlce []K8sPod,podnum int,nodeAllocatedResource NodeAllocatedResources) {


	fieldSelector, err := fields.ParseSelector("spec.nodeName=" + node.Name +
		",status.phase!=" + string(v1.PodSucceeded) +
		",status.phase!=" + string(v1.PodFailed))


	pods,err := clientset.CoreV1().Pods("").List(metav1.ListOptions{
		FieldSelector: fieldSelector.String(),
	})  //all-namespaces
	if err != nil {
		fmt.Println("get pods on node " , err.Error())
	}
	for _,pod := range pods.Items {

		if string(n) == pod.Status.HostIP {

			var status string
			if pod.Status.ContainerStatuses[0].State.Terminated != nil {
				status = "Terminated " + pod.Status.ContainerStatuses[0].State.Terminated.Reason
			} else if pod.Status.ContainerStatuses[0].State.Running != nil {
				status = "Running"
			} else if pod.Status.ContainerStatuses[0].State.Waiting != nil {
				status = "Waiting " + pod.Status.ContainerStatuses[0].State.Waiting.Reason
			} else {
				status = "Unknown State ! "
			}
			//get pod nodename

			tmpod := K8sPod{
				Name:         pod.Name,
				Status:       status,
				NodeIp:       string(n),
				PodIp:        pod.Status.PodIP,
				RestartCount: pod.Status.ContainerStatuses[0].RestartCount,
				CreateDate:   pod.CreationTimestamp.Format("2006-01-02 15:04:05"),
			}

			podSlce = append(podSlce, tmpod)
		}
	}
	podnum = len(podSlce)

	nodeAllocatedResource = getPodAllocatedResources(node,pods)
	return
}

func getPodAllocatedResources(node *v1.Node,podList *v1.PodList)(NodeAllocatedResources)  {
	reqs, limits := map[v1.ResourceName]resource.Quantity{}, map[v1.ResourceName]resource.Quantity{}
	for _,pod := range podList.Items {
		podReqs, podLimits, _ := PodRequestsAndLimits(&pod)
		for podReqName, podReqValue := range podReqs {
			if value, ok := reqs[podReqName]; !ok {
				reqs[podReqName] = *podReqValue.Copy()
			} else {
				value.Add(podReqValue)
				reqs[podReqName] = value
			}
		}
		for podLimitName, podLimitValue := range podLimits {
			if value, ok := limits[podLimitName]; !ok {
				limits[podLimitName] = *podLimitValue.Copy()
			} else {
				value.Add(podLimitValue)
				limits[podLimitName] = value
			}
		}
	}

	cpuRequests, cpuLimits, memoryRequests, memoryLimits := reqs[v1.ResourceCPU],
		limits[v1.ResourceCPU], reqs[v1.ResourceMemory], limits[v1.ResourceMemory]

	var cpuRequestsFraction, cpuLimitsFraction float64 = 0, 0
	if capacity := float64(node.Status.Capacity.Cpu().MilliValue()); capacity > 0 {
		cpuRequestsFraction = float64(cpuRequests.MilliValue()) / capacity * 100
		cpuLimitsFraction = float64(cpuLimits.MilliValue()) / capacity * 100
	}

	var memoryRequestsFraction, memoryLimitsFraction float64 = 0, 0
	if capacity := float64(node.Status.Capacity.Memory().MilliValue()); capacity > 0 {
		memoryRequestsFraction = float64(memoryRequests.MilliValue()) / capacity * 100
		memoryLimitsFraction = float64(memoryLimits.MilliValue()) / capacity * 100
	}

	var podFraction float64 = 0
	var podCapacity int64 = node.Status.Capacity.Pods().Value()
	if podCapacity > 0 {
		podFraction = float64(len(podList.Items)) / float64(podCapacity) * 100
	}

	return NodeAllocatedResources{
		CPURequests:            cpuRequests.MilliValue(),
		CPURequestsFraction:    cpuRequestsFraction,
		CPULimits:              cpuLimits.MilliValue(),
		CPULimitsFraction:      cpuLimitsFraction,
		CPUCapacity:            node.Status.Capacity.Cpu().MilliValue(),
		MemoryRequests:         memoryRequests.Value(),
		MemoryRequestsFraction: memoryRequestsFraction,
		MemoryLimits:           memoryLimits.Value(),
		MemoryLimitsFraction:   memoryLimitsFraction,
		MemoryCapacity:         node.Status.Capacity.Memory().Value(),
		AllocatedPods:          len(podList.Items),
		PodCapacity:            podCapacity,
		PodFraction:            podFraction,
	}



}

func PodRequestsAndLimits(pod *v1.Pod) (reqs map[v1.ResourceName]resource.Quantity, limits map[v1.ResourceName]resource.Quantity, err error) {
	reqs, limits = map[v1.ResourceName]resource.Quantity{}, map[v1.ResourceName]resource.Quantity{}
	for _, container := range pod.Spec.Containers {
		for name, quantity := range container.Resources.Requests {
			if value, ok := reqs[name]; !ok {
				reqs[name] = *quantity.Copy()
			} else {
				value.Add(quantity)
				reqs[name] = value
			}
		}
		for name, quantity := range container.Resources.Limits {
			if value, ok := limits[name]; !ok {
				limits[name] = *quantity.Copy()
			} else {
				value.Add(quantity)
				limits[name] = value
			}
		}
	}
	// init containers define the minimum of any resource
	for _, container := range pod.Spec.InitContainers {
		for name, quantity := range container.Resources.Requests {
			value, ok := reqs[name]
			if !ok {
				reqs[name] = *quantity.Copy()
				continue
			}
			if quantity.Cmp(value) > 0 {
				reqs[name] = *quantity.Copy()
			}
		}
		for name, quantity := range container.Resources.Limits {
			value, ok := limits[name]
			if !ok {
				limits[name] = *quantity.Copy()
				continue
			}
			if quantity.Cmp(value) > 0 {
				limits[name] = *quantity.Copy()
			}
		}
	}
	return
}